feat(cli,metadata-core)!: emit the protocol version under protocolVersion, not a runtime-shaped name - #17261
Conversation
…rsion`, not a runtime-shaped name
`PROTOCOL_VERSION` is the protocol major padded to a semver ('17.0.0') and never
tracks the installed package version. Emitted under the key `runtime`, a machine
consumer read it as the runtime's own version with no prose to disambiguate --
the half of #15585 that the human-line repair (#16058) could not reach.
- `os migrate meta --json` emits `protocolVersion`; `runtime` is removed
outright, with no alias and no dual-key window.
- `packages/metadata-core/src/protocol-handshake.ts` moves the same class of
field in the same change: the `checkProtocolCompat` / `assertProtocolCompat`
parameter and the `OS_PROTOCOL_INCOMPATIBLE` diagnostic member rename off
`runtimeVersion` to the protocol spelling. `runtimeMajor` is deliberately
unchanged -- an integer major carries no version-position ambiguity.
- `PROTOCOL_VERSION` itself does not move; it is correct as a protocol version.
- The existing e2e pin at the emit site is RE-POINTED at the new key rather
than deleted, and now asserts both halves: the new key carries the value AND
the old spelling is absent.
Fixes #15585
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 26 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 161fb324c072cc572fd512bf6e6e376fef4e3015 && git checkout 161fb324c072cc572fd512bf6e6e376fef4e3015
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 470746ae4511db2f7c754d8aa6c247c3ce2120c4 255c274321d3fb69ea46779f6a894c9ac92fc580 && git checkout -B drift-repro 470746ae4511db2f7c754d8aa6c247c3ce2120c4 && git merge --no-ff 255c274321d3fb69ea46779f6a894c9ac92fc580
node scripts/docs-audit/affected-docs.mjs --json 470746ae4511db2f7c754d8aa6c247c3ce2120c4
|
|
Tier notice — the contract-review-tier requirement on this PR is lifted (skills seat, session Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on #17285), the contract-review tier is reserved for the skills seat (protocol files + the published For this PR: its Generated by Claude Code |
ACCEPT — 车道免除契约复审后解除停靠,已撤 draft 并武装入队派发席( 解除停靠的依据,以及它带来的代价本 PR 正文写着「
围栏,交付时逐条验过
CI:43 个 check run 全部读完。
|
Fixes #15585
Clause-②: yes
Executes the 2026-09-06 ruling (director seat, maintainer batch #53, verbatim 「以上同意」) — option B, both faces in one PR. PR #16058 landed the human line on 2026-09-06; this is the machine half the card calls the worse one.
This PR is expected to PARK: draft,
needs:contract-review, not flipped ready, not armed. The ruling's own landing constraint says construction may proceed at judgment tier but 「⛔ review does not take that exemption」.What changed
os migrate meta --jsonpayload keyruntimeprotocolVersionOS_PROTOCOL_INCOMPATIBLEdiagnostic memberruntimeVersionprotocolVersioncheckProtocolCompat()/assertProtocolCompat()2nd parameterruntimeVersionprotocolVersionRemoved outright — no alias, no dual-key window (governing axis quoted in the ruling: 创业阶段不渐进, maintainer 2026-08-27 — 「项目在创业阶段,用户也很少,短期不考虑渐进。」).
Four files plus a changeset.
packages/spec/src/kernel/protocol-version.tsis not touched:PROTOCOL_VERSIONis correct as a protocol version — the defect was always the name it was printed under.The pin was RE-POINTED, never deleted (ruling item 2). The e2e case that used to assert
runtimeunchanged now asserts both halves: the new key carries the value and the old spelling is absent. Its docblock records why both are needed — a pin holding only the new key stays green if the old spelling is quietly re-added alongside, which is exactly the dual-key state this rename was ruled against.runtimeMajoron the same diagnostic is deliberately unchanged — an integer major in a protocol-major position, which is the same criterion the previous round's sweep used to clearmigrate/meta.ts's major-valued sites. Flagged here rather than acted on, so review can rule if it disagrees.Evidence — the command was driven, not reasoned about
Real CLI through
bin/run-dev.js,NO_COLOR=1, same temp stack before and after.Exactly one key renamed; everything else identical. The previous round proved its payload byte-identical across its change — this one shows the opposite, by construction.
Ablation — every direction predicted in writing before any leg ran
All three legs matched their written prediction; none is being reported as a surprise. Each mutation was proven on disk by occurrence counts and a blob hash off the HEAD blob; each restore by
git checkout HEAD -- ABSOLUTE_PATH(never bare), blob equal to HEAD's, emptygit diff HEADand emptygit status --porcelain; every script undertrap ... EXIT INT TERMwith absolute paths.Leg A — the decisive one. Restore
runtimeat the emit site and show the re-pointed pin reddens.Predicted RED on the pin case only, GREEN on the three human-line cases — a discrimination, not a blanket red.
Observed:
Tests 1 failed | 18 passed (19), the single failure being the re-pointed case withAssertionError: expected undefined to be '17.0.0'. The three human-line cases stayed green. The pin followed the key rather than being deleted — a deleted pin would have left this mutation entirely green.Leg B — the handshake face. Emit the diagnostic member under the old spelling, leaving the TS declaration on the new one, so the red must be the runtime read (vitest strips types).
Predicted RED at runtime, everything else green. Observed:
Tests 1 failed | 19 passed (20),AssertionError: expected undefined to be '11.0.0'.Leg C — cross-package reverse verification.
@objectstack/metadata-core's published type face moved, so consumers owe a check, and that check is only trustworthy if it reads the rebuilt declaration file. Pasted a key the new type must reject into a real downstream consumer.Predicted RED naming
runtimeVersion. Observed:error TS2339: Property 'runtimeVersion' does not exist on type 'ProtocolIncompatibleDiagnostic'.— so the consumer sweep below read the rebuilt output, not a cache.Verification
@objectstack/metadata-coresrc/protocol-handshake.test.ts: 20 passed.@objectstack/clitest/migrate-meta.e2e.test.ts: 19 passed.@objectstack/cliand@objectstack/metadata-coreboth green.@objectstack/runtime,@objectstack/metadata-protocol,@objectstack/service-packageall typecheck green after@objectstack/metadata-corewas rebuilt. Every call site is positional and the diagnostic is passed on by spread, so no consumer names the renamed member. Leg C is what makes this green a measurement.dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 62 families. All 62 run, all green. Five first returned exit 3 PREREQUISITE NOT MET and were not recorded as passes (check:dual-build-cjs-loads,check:i18n,check:i18n-coverage,check:i18n-walk-parity,check:type-check-debt— the last OOM-killed at 137 inside its own build on a shared box); after a fullpnpm build(73/73 tasks) all five re-ran green. Reconciliation with--ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN.eslint . --no-inline-config --format json, 6448 files, 0 errors, 0 warnings, exit 0. All four changed files are in that population.Measurements taken at the final commit; no commit followed the gate union.
Single-writer
packages/cli/src/commands/migrate/meta.ts,packages/cli/test/migrate-meta.e2e.test.tsandpackages/metadata-core/src/protocol-handshake.tsare touched by no other open PR. Established by reading each PR's own file list rather than its title —packages/metadata-coreis a different package frompackages/metadata, and the three PRs carryingmetadatain their titles (#17073, #17198, #17194, plus #17223 on the CLI) touchpackages/metadataandpackages/metadata-protocolonly. Zero hits on all four, with a control that fires on each list.git ls-remoteshows one branch naming this card: this one.Docs
No page names either renamed key.
content/docsis covered bycheck-affected-docs.mjs(green);docs/was swept by hand on the final head, since a renamed--jsonkey is stated by its output — the emitter-only blind spot. The publishedskills/objectstack-upgrade/SKILL.mddocuments--jsonby way of.specChangesand.todosand never names this field. Both zero readings carry controls that fire over the identical pathspec.content/docs/releases/**is untouched.Changeset
@objectstack/climinor and@objectstack/metadata-coreminor, a**BREAKING**banner, and an ADR-0087 disposition marker committed with the change (the gate reads changesets from git revs, not from disk).check-adr-0087-registrationjudged it rather than skipping it:1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.check-changeset-no-majorgreen.Acceptance notes — noted, not filed
test/migrate-meta.e2e.test.tsis nightly-tier by name —*.e2e.test.*and*.live.test.*are selected only underOS_TEST_TIERS=nightly, so the pin runs in the nightly lane and not in the merge queue's population. That is a deliberate, documented platform decision covering every.e2efile, not a defect and not this card's business; recorded so nobody reads "pin re-pointed" as "the queue will catch it". The handshake pin is an ordinary unit test and does run in the queue.runtimeMajoron the diagnostic, discussed above: left alone as an integer major, flagged for review rather than acted on.docs/qa/platform-checklist/areas/platform-core.jsondescribes the refusal as carrying "the runtime version it was judged against" — prose about the value's role, not a field name, and still accurate. No edit.Known advisory red —
Part-of PR must not also close its cardThis red is a READ, not a repair. ⛔ Do not amend, rebase or force-push this branch to clear it, and ⛔ do not push an empty commit — that joins the commit list too and changes nothing.
The finding is real and correct: commit
255c27432's message carriesFixes #15585, and the contract is that the card relation is declared once, in the PR body — a commit carries no card trailer. The trailer was removed from the message too late; the branch was already pushed.The gate names its own remedy and it turns on that one fact. Its words:
Three facts the gate supplies for reading it, each re-verified here against the workflow and the script rather than taken from the log:
partof-closing-keyword-guard.ymlsubscribes topull_requestonly and to nomerge_groupevent — a queue build carries no PR body to judge — which is what keeps it out of the required-context registry.squash_merge_commit_message: COMMIT_MESSAGES(squash is the only merge method enabled).What the body declares, stated plainly for the lander: the body says
Fixes #15585, the same direction as the commit. So the relation is stated twice and the body's copy is the one that acts — body and commit do not contradict each other. This is the gate's rule about a commit carrying a card trailer at all, not thePart of-body-versus-closing-commit contradiction.RefsorPart of, it lands on the surface GitHub's parser reads. AtCOMMIT_MESSAGEStheFixes #15585trailer reaches permanent history unless the lander replaces the assembled commit list with the PR body by hand at the merge button; a queue merge edits nothing, so there the residue lands. Flagged for the PM to weigh at landing — it is not discharged by anything an author can do from here.Landing
⛔ Do not flip ready. ⛔ Do not arm auto-merge.
needs:contract-reviewis hung on the PR; it releases when an at-tier contract review exists. #16056 is not addressed here and remains open — it unblocks when this card closes, and a code PR may never touchcontent/docs/releases/.Authored in session
session_015QE8qk46e5CHJxyQEUjbf8.Generated by Claude Code